package br.usp.tuf.ui;

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.view.JasperViewer;
import br.usp.tuf.annotations.ParameterJNeticTest;
import br.usp.tuf.core.JNeticTest;
import br.usp.tuf.entities.MetodoVisao;
import br.usp.tuf.entities.NodeItem;
import br.usp.tuf.entities.NodeItemTestCase;
import br.usp.tuf.entities.Resultado;
import br.usp.tuf.entities.TestCase;
import br.usp.tuf.utils.GeradorAleatorio;
import br.usp.tuf.utils.ReflectionUtils;

import com.google.java.contract.InvariantError;
import com.google.java.contract.PostconditionError;
import com.google.java.contract.PreconditionError;

public class MainInterface extends JFrame {

	private static final String CAMINHO_ABSOLUTO_RELATORIOS = "C://Users//Venilton Falvo Jr//workspace//TUF//src//br//usp//tuf//reports//";

	private static final String MSG_ERRO_DE_CONVERSAO = "Ocorreu um erro ao converter o valor digitado para %s.";

	private static final long serialVersionUID = -8294358298048267286L;

	private Class<?> classToBeTested = null;

	private String classFilledByTheTester = "";

	private JPanel contentPane;
	private JTable table;
	private JTree tree;
	private JScrollPane scrollPane_1;
	private JScrollPane scrollLabelValor;
	private List<MetodoVisao> listOfMetodoVisao;
	@SuppressWarnings("rawtypes")
	private JComboBox comboBox = null;
	private JLabel labelValor = null;
	private DefaultMutableTreeNode nodeSelected = null;
	private DefaultTreeModel model;
	private List<NodeItem> paramList = new ArrayList<NodeItem>();

	private void addNodeRaiz(DefaultMutableTreeNode parent,
			List<Class<?>> parametros, Annotation[][] ann) {

		int i = 0;
		for (Class<?> classChild : parametros) {
			String varName = "";
			for (int j = 0; j < ann[i].length; j++) {
				if (ann[i][j] instanceof ParameterJNeticTest) {
					varName = ((ParameterJNeticTest) ann[i][j]).value();
				}
			}
			if (varName == "") {
				varName = "arg" + i;
			}
			DefaultMutableTreeNode node = new DefaultMutableTreeNode(
					classChild.getName() + " " + varName);
			model.insertNodeInto(node, parent, parent.getChildCount());
			NodeItem nodeItem = new NodeItem(classChild.getName(), varName);
			paramList.add(nodeItem);
			i++;
		}
	}

	private void addNodes(DefaultMutableTreeNode parent,
			NodeItem nodeListParent, List<Class<?>> parametros,
			Annotation[][] ann) {
		int i = 0;
		for (Class<?> classChild : parametros) {
			String varName = "";
			for (int j = 0; j < ann[i].length; j++) {
				if (ann[i][j] instanceof ParameterJNeticTest) {
					varName = ((ParameterJNeticTest) ann[i][j]).value();
				}
			}
			if (varName == "") {
				varName = "arg" + i;
			}
			DefaultMutableTreeNode node = new DefaultMutableTreeNode(
					classChild.getName() + " " + varName);
			model.insertNodeInto(node, parent, parent.getChildCount());
			NodeItem nodeItem = new NodeItem(classChild.getName(), varName);
			nodeListParent.addFilho(nodeItem);
			i++;
		}
	}

	private NodeItem getNodeList(DefaultMutableTreeNode node) {
		NodeItem no = null;

		if (node.getLevel() == 1) {
			no = paramList.get(node.getParent().getIndex(node));
		} else {
			TreeNode[] path = node.getPath();
			NodeItem nodeAux = paramList.get(path[0].getIndex(path[1]));
			System.out.println("node: " + nodeAux.getNome());
			for (int i = 2; i < path.length; i++) {
				int index = path[i].getParent().getIndex(path[i]);
				nodeAux = nodeAux.getFilhos().get(index);
			}
			no = nodeAux;
		}
		return no;
	}

	private void treeListener() {
		tree.addMouseListener(new MouseAdapter() {

			@Override
			public void mousePressed(MouseEvent e) {
				int selRow = tree.getRowForLocation(e.getX(), e.getY());
				TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
				if (selRow != -1) {
					nodeSelected = (DefaultMutableTreeNode) tp
							.getLastPathComponent();
					if (nodeSelected.getLevel() > 0) {
						NodeItem nodeList = getNodeList(nodeSelected);
						String classToBeReloadedName = nodeSelected.toString().split(" ")[0];
						if (e.getClickCount() == 1) {
							if (GeradorAleatorio.isPrimitivo(classToBeReloadedName)) {
								labelValor.setText("" + nodeList.getValor());
								scrollLabelValor.setVisible(true);
							} else {
								scrollLabelValor.setVisible(false);
							}
						} else if (e.getClickCount() == 2) {
							if (GeradorAleatorio
									.isPrimitivo(classToBeReloadedName)) {

								boolean valorValido = false;
								while (!valorValido) {
									String valor = JOptionPane.showInputDialog(null, "Insira o valor", "Entrada", JOptionPane.INFORMATION_MESSAGE);
									if (valor == null) {
										valorValido = true;
									} else {
										Object instanciaTipada = null;
										if (classToBeReloadedName.equals(byte.class.getName()) || classToBeReloadedName.equals(Byte.class.getName())) {
											try {
												instanciaTipada = Byte.parseByte(valor);
												valorValido = true;
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, Byte.class.getSimpleName()));
											}
										} else if (classToBeReloadedName.equals(short.class.getName()) || classToBeReloadedName.equals(Short.class.getName())) {
											try {
												instanciaTipada = Short.parseShort(valor);
												valorValido = true;
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO,
																Short.class.getSimpleName()));
											}
										} else if (classToBeReloadedName.equals(int.class.getName()) || classToBeReloadedName.equals(Integer.class.getName())) {
											try {
												instanciaTipada = Integer.parseInt(valor);
												valorValido = true;
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, Integer.class.getSimpleName()));
											}
										} else if (classToBeReloadedName.equals(long.class.getName()) || classToBeReloadedName.equals(Long.class.getName())) {
											try {
												instanciaTipada = Long.parseLong(valor);
												valorValido = true;
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, Long.class.getSimpleName()));
											}
										} else if (classToBeReloadedName.equals(float.class.getName()) || classToBeReloadedName.equals(Float.class.getName())) {
											try {
												instanciaTipada = Float.parseFloat(valor);
												valorValido = true;
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, Float.class.getSimpleName()));
											}
										} else if (classToBeReloadedName.equals(double.class.getName()) || classToBeReloadedName.equals(Double.class.getName())) {
											try {
												instanciaTipada = Double.parseDouble(valor);
												valorValido = true;
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, Double.class.getSimpleName()));
											}
										} else if (classToBeReloadedName.equals(char.class.getName()) || classToBeReloadedName.equals(Character.class.getName())) {
											try {
												if (valor.length() == 1) {
													instanciaTipada = Character.valueOf(valor.charAt(0));
													valorValido = true;
												}
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, Double.class.getSimpleName()));
											}
										} else if (classToBeReloadedName.equals(String.class.getName())) {
											try {
												instanciaTipada = valor;
												valorValido = true;
											} catch (Exception excecaoDeParse) {
												gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, String.class.getSimpleName()));
											}
										}
										nodeList.setValor(instanciaTipada);
										labelValor.setText("" + nodeList.getValor());
										scrollLabelValor.setVisible(true);
									}
								}
							} else {
								if (nodeList.getFilhos().size() > 0) {
									while (nodeSelected.getChildCount() > 0) {
										MutableTreeNode child = (MutableTreeNode) nodeSelected
												.getFirstChild();
										model.removeNodeFromParent(child);
									}
									nodeList.getFilhos().clear();
								} else {
									try {
										Class<?> classToBeReloaded = Class.forName(classToBeReloadedName);
										Constructor<?> constructor = ReflectionUtils.recuperarConstrutorJNeticTest(classToBeReloaded);
										List<Class<?>> parametros = Arrays.asList(ReflectionUtils.carregarParametrosDoConstrutor(constructor));

										addNodes(nodeSelected, nodeList, parametros, constructor.getParameterAnnotations());

									} catch (ClassNotFoundException excecao) {
										gerarMensagemDeErro("Ocorreu um erro ao carregar a classe do nó selecionado.");
									}
								}
								model.reload();
								if (!tree.isExpanded(tp)) {
									tree.expandPath(tp);
								}
								tree.repaint();
							}
						}
					} else {
						scrollLabelValor.setVisible(false);
					}
				}
			}

		});
		scrollPane_1.setViewportView(tree);
	}

	private void fillTable() {
		this.listOfMetodoVisao = ReflectionUtils.carregarMetodos(this.classToBeTested);

		for (MetodoVisao metodoVisao : this.listOfMetodoVisao) {
			DefaultTableModel linha = (DefaultTableModel) this.table.getModel();
			String paramets = getParameths(metodoVisao.getParametros());
			linha.addRow(new String[] { metodoVisao.getNome(),
					metodoVisao.getModificador(), paramets, "" + metodoVisao.getRetorno() });
		}
	}

	private String getParameths(List<Class<?>> parameths) {
		String paramet = "";
		if (parameths.size() == 0) {
			paramet += "----";
		} else {
			for (Class<?> string : parameths) {
				paramet += " " + string;
			}
		}
		return paramet;
	}

	private void gerarMensagemDeErro(String msg) {
		JOptionPane.showMessageDialog(null, msg, "Erro", JOptionPane.ERROR_MESSAGE);
	}

	@SuppressWarnings("rawtypes")
	public MainInterface(String classFilledByTheTester) {

		this.classFilledByTheTester = classFilledByTheTester;
		final String classeRelatorio = classFilledByTheTester;
		try {

			this.classToBeTested = Class.forName(this.classFilledByTheTester);

		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		setTitle("TUF");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setBounds(100, 100, 943, 535);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBounds(10, 59, 301, 110);
		contentPane.add(scrollPane);

		table = new JTable();
		scrollPane.setViewportView(table);
		table.setModel(new MyTableModel());

		table.addMouseListener(new MouseAdapter() {

			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					Point p = e.getPoint();
					int row = table.rowAtPoint(p);

					MetodoVisao metodoVisao = listOfMetodoVisao.get(row);

					DefaultMutableTreeNode raiz = new DefaultMutableTreeNode(
							metodoVisao.getNome(), true);
					tree = new JTree(raiz);
					model = (DefaultTreeModel) tree.getModel();

					List<Class<?>> parameters = metodoVisao.getParametros();
					Class<?>[] parametersArray = metodoVisao.getParametrosArray();
					Method method = null;
					try {
						method = classToBeTested.getMethod(metodoVisao.getNome(), parametersArray);
						addNodeRaiz(raiz, parameters, method.getParameterAnnotations());

						model.reload();
						tree.repaint();
						treeListener();

					} catch (SecurityException e1) {
						gerarMensagemDeErro("Ocorreu um erro ao recarregar os itens da árvore.");
					} catch (NoSuchMethodException e1) {
						gerarMensagemDeErro("Ocorreu um erro ao recarregar os itens da árvore.");
					}
				}
			}

		});

		scrollPane_1 = new JScrollPane();
		scrollPane_1.setBounds(317, 59, 268, 250);
		contentPane.add(scrollPane_1);

		comboBox = new JComboBox();
		comboBox.setVisible(false);
		comboBox.setBounds(595, 59, 292, 20);
		contentPane.add(comboBox);

		labelValor = new JLabel();
		scrollLabelValor = new JScrollPane();
		scrollLabelValor.setBounds(585, 59, 268, 40);
		scrollLabelValor.setViewportView(labelValor);
		scrollLabelValor.setVisible(false);
		contentPane.add(scrollLabelValor);

		JButton btnTestar = new JButton("Testar!");
		btnTestar.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evento) {
				int valorDef = 1;
				boolean valorNull = false;
				int numTimes = 0;

				while (numTimes < 1 || valorNull) {
					String valor = JOptionPane.showInputDialog(null, "Insira o número de execuções", "Valor padrão = " + valorDef, JOptionPane.INFORMATION_MESSAGE);
					if (valor == null) {
						valorNull = true;
						break;
					}
					if (valor.equals("")) {
						numTimes = valorDef;
					} else {
						try {
							numTimes = Integer.parseInt(valor);
							if (numTimes < 1) {
								gerarMensagemDeErro("Favor inserir um valor maior que zero!");
							}
						} catch (Exception excecaoDeParse) {
							gerarMensagemDeErro(String.format(MSG_ERRO_DE_CONVERSAO, Integer.class.getSimpleName()));
						}
					}
				}
				if (!valorNull) {
					List<TestCase> validTestCases = new ArrayList<TestCase>();
					List<TestCase> invalidTestCases = new ArrayList<TestCase>();

					for (int i = 0; i < numTimes; i++) {
						TestCase testCase;
						// Lista com os objetos utilizados nos relatórios.
						List<NodeItemTestCase> paramListInst = new ArrayList<NodeItemTestCase>();
						// Lista com os objetos já instanciados.
						List<Object> paramListReflection = new ArrayList<Object>();
						try {
							for (NodeItem no : paramList) {
								// Cria um NodeItemTestCase que será utilizado na geração do relatório.
								NodeItemTestCase objeto = new NodeItemTestCase();
								objeto.setNome(no.getId());
								objeto.setAutomatic(GeradorAleatorio.isPrimitivo(no.getNome()) && no.getValor() == null);
								objeto.setValor(ReflectionUtils.instanciarNo(no));

								paramListInst.add(objeto);
								paramListReflection.add(objeto.getValor());
							}
							JNeticTest.invocarMetodo(classToBeTested, tree.getModel().getRoot().toString(),
									paramListReflection.toArray());
							testCase = new TestCase(Resultado.SUCESSO, "N/A", paramListInst);
							validTestCases.add(testCase);
						} catch (InvocationTargetException e) {
							Throwable tr = e.getCause();
							Class<?> error = tr.getClass();
							if (error.equals(PreconditionError.class)) {
								testCase = new TestCase(Resultado.ERRO_PRE_CONDICAO, tr.getLocalizedMessage(),
										paramListInst);
								invalidTestCases.add(testCase);
							} else if (error.equals(PostconditionError.class)) {
								testCase = new TestCase(Resultado.ERRO_POS_CONDICAO, tr.getLocalizedMessage(),
										paramListInst);
								validTestCases.add(testCase);
							} else if (error.equals(InvariantError.class)) {
								testCase = new TestCase(Resultado.ERRO_DE_INVARANTE, tr.getLocalizedMessage(),
										paramListInst);
								validTestCases.add(testCase);
							} else {
								testCase = new TestCase(Resultado.OUTROS, e.getClass().getSimpleName(), paramListInst);
								validTestCases.add(testCase);
							}
						} catch (Exception e) {
							testCase = new TestCase(Resultado.OUTROS, e.getClass().getSimpleName(), paramListInst);
							invalidTestCases.add(testCase);
						}
					}
					System.out.println("\n\n\nvalid:");
					for (TestCase testCase : validTestCases) {
						imprimirResultados(testCase);
					}
					System.out.println("\n\n\ninvalid:");
					for (TestCase testCase : invalidTestCases) {
						imprimirResultados(testCase);
					}

					try {
						final List<TestCase> listaCompleta = validTestCases;
						listaCompleta.addAll(invalidTestCases);
						
						// cria datasource a partir da collection
						JRBeanCollectionDataSource jrds = new JRBeanCollectionDataSource(listaCompleta);

						// Mapeia os parâmetros
						Map<String, Object> parametros = new HashMap<String, Object>();
						parametros.put("NOME_CLASSE", classeRelatorio);
						parametros.put("NOME_METODO", "TODO: Pegar o nome do método!");
						parametros.put("CAMINHO_RELATORIOS", CAMINHO_ABSOLUTO_RELATORIOS);

						// Preenche relatorio com parâmetros e datasource.
						JasperPrint printer = JasperFillManager.fillReport(CAMINHO_ABSOLUTO_RELATORIOS + "CasosDeTeste.jasper", parametros, jrds);
						
						// Abre o visualizador.
						JasperViewer jv = new JasperViewer(printer, false);
						jv.setTitle("Casos de teste gerados a partir do método");
						jv.setVisible(true);
					} catch (Exception e) {
						System.out.println("ERRO ");
						e.printStackTrace();
					}

				} else {
					System.out.println("saiu2");
				}
			}

			private void imprimirResultados(TestCase testCase) {
				String str = "\nParametros:\n";
				str += testCase.getParamList().toString();
				str += "\nInfo: ";
				switch (testCase.getResultType()) {
				case SUCESSO:
					str += "";
					break;
				case ERRO_PRE_CONDICAO:
					str += "Erro Precondition - ";
					break;
				case ERRO_POS_CONDICAO:
					str += "Erro Poscondition - ";
					break;
				case ERRO_DE_INVARANTE:
					str += "Erro Invariant - ";
					break;
				case OUTROS:
					str += "Erro (Outros) - ";
					break;
				}
				str += testCase.getResultInfo();
				System.out.println(str);
			}
		});
		this.setVisible(true);
		btnTestar.setBounds(412, 320, 89, 23);
		contentPane.add(btnTestar);
		fillTable();
	}
}
